// Copyright 2013 Vedran Vuk. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package irc

import (
	s2 "code.google.com/p/vvrepo/strings"
	"time"
)

// Channel mode of a user on a channel.
const (
	CmVoice = 1 << iota
	CmHalfOp
	CmOp
	CmAdmin
	CmOwner
)

// Defines a joined channel in joined channels list of IALUser.
type IALChannelLink struct {
	// Pointer to the actual channel structure in IAL.[]Channels
	Channel *IALChannel
	// Channel mode the user has in the channel.
	ChannelMode int
}

// Defines a user in the IAL.
type IALUser struct {
	// List of channels the user is in.
	InChannels []IALChannelLink
	// Hostmask/Nickname of the user.
	User IRCEntity
}

// Get index of joined channel, -1 if not found.
func (self *IALUser) channelIndex(channelName string) int {
	lo := 0
	hi := len(self.InChannels) - 1
	for lo <= hi {
		i := (lo + hi) >> 1
		r := s2.CompareFold(self.InChannels[i].Channel.Name, channelName)
		if r == 0 {
			return i
		} else {
			if r < 0 {
				lo = i + 1
			} else {
				hi = i - 1
			}
		}
	}
	return -1
}

// Adds a channel to list of joined if not in. channelMode is updated if channel
// is either found or added.
func (self *IALUser) addOrUpdateChannel(channel *IALChannel, channelMode int) {
	if i := self.channelIndex(channel.Name); i == -1 {
		if len(self.InChannels) > 0 {
			lo := 0
			hi := len(self.InChannels)
			for lo < hi {
				i = (lo + hi) >> 1
				if s2.CompareFold(
					channel.Name, self.InChannels[i].Channel.Name) < 0 {
					hi = i
				} else {
					lo = i + 1
				}
			}
			i = lo
			self.InChannels = append(self.InChannels[:i],
				append([]IALChannelLink{{channel, channelMode}},
					self.InChannels[i:]...)...)
		} else {
			self.InChannels = []IALChannelLink{{channel, channelMode}}
		}
		channel.UserCount++
	} else {
		self.InChannels[i].ChannelMode = channelMode
	}
}

// Deletes a channel from list of joined.
func (self *IALUser) deleteChannel(channelName string) {
	if i := self.channelIndex(channelName); i > -1 {
		self.InChannels[i].Channel.UserCount--
		self.InChannels[i].Channel = nil
		copy(self.InChannels[i:], self.InChannels[i+1:])
		self.InChannels = self.InChannels[:len(self.InChannels)-1]
	}
}

// Clear the list of joined channels.
func (self *IALUser) clearChannels() {
	for _, v := range self.InChannels {
		v.Channel.UserCount--
		v.Channel = nil
	}
	self.InChannels = []IALChannelLink{}
}

// Defines a channel in the IAL.
type IALChannel struct {
	// Channel name.
	Name string
	// Topic of the channel.
	Topic string
	// Timestamp of when the channel topic was set.
	TopicTimestamp time.Time
	// Nickname of the user who set the channel topic.
	TopicSetter string
	// Channel modes.
	Modes string
	// Number of IALUser bound to this channel IALChannel.
	UserCount int
}

// Internal Address List keeps track of all users and channels and their
// relationship for an IRC session. Data structures used are always-sorted
// slices. Insertion uses bisect-right, lookups are binary searches.
type IAL struct {
	// List of users in the IAL.
	users []IALUser
	// List of channels in the IAL.
	channels []IALChannel
}

// Get the index of the user in the IAL and return it, -1 if not found.
func (self *IAL) userIndex(nickname string) int {
	lo := 0
	hi := len(self.users) - 1
	for lo <= hi {
		i := (lo + hi) >> 1
		r := s2.CompareFold(self.users[i].User.Nickname(), nickname)
		if r == 0 {
			return i
		} else {
			if r < 0 {
				lo = i + 1
			} else {
				hi = i - 1
			}
		}
	}
	return -1
}

// Get the index of the channel in the IAL and return it, -1 if not found.
func (self *IAL) channelIndex(channelName string) int {
	lo := 0
	hi := len(self.channels) - 1
	for lo <= hi {
		i := (lo + hi) >> 1
		r := s2.CompareFold(self.channels[i].Name, channelName)
		if r == 0 {
			return i
		} else {
			if r < 0 {
				lo = i + 1
			} else {
				hi = i - 1
			}
		}
	}
	return -1
}

// Search for a user in IAL and return it if found, nil otherwise.
func (self *IAL) User(nickname string) *IALUser {
	if i := self.userIndex(nickname); i > -1 {
		return &self.users[i]
	}
	return nil
}

// Search for a channel in IAL and return it if found, nil otherwise.
func (self *IAL) Channel(channelName string) *IALChannel {
	if i := self.channelIndex(channelName); i > -1 {
		return &self.channels[i]
	}
	return nil
}

// Adds a new user to the IAL if it doesn't exist and returns it.
func (self *IAL) AddUser(user *IRCEntity) *IALUser {
	if u := self.User(user.Nickname()); u != nil {
		return u
	}

	i := 0
	if len(self.users) > 0 {
		lo := 0
		hi := len(self.users)
		for lo < hi {
			i = (lo + hi) >> 1
			if s2.CompareFold(user.Nickname(), self.users[i].User.Nickname()) < 0 {
				hi = i
			} else {
				lo = i + 1
			}
		}
		i = lo
		self.users = append(self.users[:i],
			append([]IALUser{{User: *user}}, self.users[i:]...)...)
	} else {
		self.users = append(self.users, IALUser{User: *user})
	}

	return &self.users[i]
}

// Adds a new channel to the IAL if it doesn't exist and returns it.
func (self *IAL) AddChannel(channelName string) *IALChannel {
	if r := self.Channel(channelName); r != nil {
		return r
	}

	i := 0
	if len(self.channels) > 0 {
		lo := 0
		hi := len(self.channels)
		for lo < hi {
			i = (lo + hi) >> 1
			if s2.CompareFold(channelName, self.channels[i].Name) < 0 {
				hi = i
			} else {
				lo = i + 1
			}
		}
		i = lo
		self.channels = append(self.channels[:i],
			append([]IALChannel{{Name: channelName}}, self.channels[i:]...)...)
	} else {
		self.channels = append(self.channels, IALChannel{Name: channelName})
	}

	return &self.channels[i]
}

// Deletes a user from the IAL and removes him from all the channels in the IAL.
// If the user was not in the IAL prior to deletion, nothing is done.
// If a channel in the IAL is left without any users after this removal of a
// user it is removed from the IAL.
func (self *IAL) DeleteUser(nickname string) {
	if i := self.userIndex(nickname); i > -1 {
		self.users[i].clearChannels()
		copy(self.users[i:], self.users[i+1:])
		self.users = self.users[:len(self.users)-1]
	}
}

// Deletes a channel from the IAL.
// If the channel does not exist prior to deletion, nothing is done.
func (self *IAL) DeleteChannel(channelName string) {
	// Delete the channel from every user's joined channel list first.
	for _, v := range self.users {
		self.DeleteUserFromChannel(v.User.Nickname(), channelName)
	}

	if i := self.channelIndex(channelName); i > -1 {
		copy(self.channels[i:], self.channels[i+1:])
		self.channels = self.channels[:len(self.channels)-1]
	}
}

// Adds a user to a channel.
// If either user or channel don't exist prior to this call, they are created.
// If in the IAL the user is already on the channel, nothing is done, but the
// user's channel mode for the channel is updated, so this function can also be
// used to update user's channel mode on a channel.
// If no channel mode for the user is to be set, use 0 for channelMode.
func (self *IAL) AddUserToChannel(user *IRCEntity, channelName string, channelMode int) {
	c := self.AddChannel(channelName)
	u := self.AddUser(user)
	if i := u.channelIndex(channelName); i == -1 {
		u.addOrUpdateChannel(c, channelMode)
	}
}

// Removes a user from a channel in the IAL.
// If either user or the channel don't exist in the IAL prior to deletion,
// nothing is done.
// If the channel is left without any users after removal of user from the
// channel, the channel is removed rom the IAL.
func (self *IAL) DeleteUserFromChannel(nickname, channelName string) {
	if i := self.userIndex(nickname); i > -1 {
		self.users[i].deleteChannel(channelName)

		if j := self.channelIndex(channelName); j > -1 {
			if self.channels[j].UserCount < 0 {
				copy(self.channels[j:], self.channels[j+1:])
				self.channels = self.channels[:len(self.channels)-1]
			}
		}
	}
}

// Change user's nickname in the IAL.
// If the user was not in the IAL prior to this call nothing is done.
func (self *IAL) ChangeUserNickname(oldNick, newNick string) {
	if i := self.userIndex(oldNick); i > -1 {
		u := self.users[i]
		u.User = IRCEntity(newNick + "!" + u.User.Username() + "@" + u.User.Hostname())
		copy(self.users[i:], self.users[i+1:])
		self.users = self.users[:len(self.users)-1]

		i := 0
		if len(self.users) > 0 {
			lo := 0
			hi := len(self.users)
			for lo < hi {
				i = (lo + hi) >> 1
				if s2.CompareFold(newNick, self.users[i].User.Nickname()) < 0 {
					hi = i
				} else {
					lo = i + 1
				}
			}
			i = lo
			self.users = append(self.users[:i],
				append([]IALUser{u}, self.users[i:]...)...)
		} else {
			self.users = []IALUser{u}
		}
	}
}

// Clears the IAL.
func (self *IAL) Clear() {
	self.users = []IALUser{}
	self.channels = []IALChannel{}
}
